<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <meta name="xbsj-labels" content="内部测试"></meta>
    <title>多选-框选&任意选择</title>
    <!-- 0 引入js文件 -->
    <script src="../../XbsjEarth/XbsjEarth.js"></script>
    <style>
        html,
        body {
            width: 100%;
            height: 100%;
            margin: 0px;
            padding: 0px;
        }
    </style>
</head>

<body>
    <div id="earthContainer" style="width: 100%; height: 100%; background: grey">
    </div>
    <script>
   class CanvasManager {
    constructor(viewer) {
        var c = document.createElement('canvas');
        c.style.pointerEvents = 'none';
        c.style = 'position: absolute; width: 100%; height: 100%; left: 0px; top: 0px; pointer-events: none';

        this._positions = [];
        this._dirty = true;

        var ctx = c.getContext('2d');    

        this._listener = viewer.scene.preUpdate.addEventListener(() => {
            if (c.width !== viewer.canvas.width) {
                c.width = viewer.canvas.width;
                this._dirty = true;
            }

            if (c.height !== viewer.canvas.height) {
                c.height = viewer.canvas.height;           
                this._dirty = true;
            }

            if (this._dirty) {
                this._dirty = false;

                // const r = 1.0 / viewer.resolutionScale;
                const r = viewer.scene.pixelRatio; // pixelRatio = resolutionScale * devicePixelRatio;

                // redraw
                ctx.fillStyle = "rgba(255,255,255,0.5)";
                ctx.strokeStyle = 'rgb(255, 255, 255)'; 

                ctx.clearRect(0, 0, c.width, c.height);
                ctx.beginPath();
                const ps = this._positions;
                const psl = ps.length / 2 | 0;
                for (let i=0; i<psl; ++i) {
                    ctx.lineTo(ps[i*2+0]*r, ps[i*2+1]*r);
                }
                ctx.fill();
                ctx.stroke();
            }
        });

        viewer.container.appendChild(c);

        this._viewer = viewer;
        this._canvas = c;
    }

    get positions() {
        return this._positions;
    }

    refresh() {
        this._dirty = true;
    }

    destroy() {
        this._viewer.container.removeChild(this._canvas);
        this._listener = this._listener && this._listener();
        this._canvas = undefined;
        this._viewer = undefined;
    }
}

function getWindowRectangle(positions, rectangle) {
    if (typeof rectangle === 'undefined') {
        rectangle = new Cesium.BoundingRectangle();
    }

    let min = [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY];
    let max = [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY];

    let pl = (positions.length / 2) | 0;
    for (let i=0; i<pl; ++i) {
        min[0] = min[0] > positions[i*2+0] ? positions[i*2+0] : min[0];
        min[1] = min[1] > positions[i*2+1] ? positions[i*2+1] : min[1];
        max[0] = max[0] > positions[i*2+0] ? max[0] : positions[i*2+0];
        max[1] = max[1] > positions[i*2+1] ? max[1] : positions[i*2+1];
    }
    var width = max[0] - min[0];
    var height = max[1] - min[1];

    rectangle.x = min[0];
    rectangle.y = min[1];
    rectangle.width = width;
    rectangle.height = height;

    return rectangle;
}

function getViewportRectangle(viewer, windowPosition1, windowPosition2, rectangle) {
    getWindowRectangle([windowPosition1.x, windowPosition1.y, windowPosition2.x, windowPosition2.y], rectangle);

    // var rs = viewer.resolutionScale;
    var rs = viewer.scene.pixelRatio;
    rectangle.x *= rs;
    rectangle.y *= rs;
    rectangle.width *= rs;
    rectangle.height *= rs;
    rectangle.y = viewer.scene.drawingBufferHeight - rectangle.y - rectangle.height;

    return rectangle;
}

class XbsjBoxPickAction {
    /**
     * 创建XbsjBoxPickAction以后，自动获得框选操作
     * @param {Cesium.Viewer} viewer 
     */
    constructor(viewer) {
        this._viewer = viewer;

        this._mode = 'rect'; // 'polygon' // polygon表示任意多边形
        // this._mode = 'polygon';

        // 创建canvasManager
        this._canvasManager = new CanvasManager(viewer);
        
        // 创建选框
        var rect = new Cesium.ViewportQuad(new Cesium.BoundingRectangle(0, 0, 200, 200));
        rect.material.uniforms.color = new Cesium.Color(1, 1, 1, 0.3);
        viewer.scene.primitives.add(rect);
        rect.show = false;
        this._rect = rect;

        // 创建event
        this._objectsPickedEvent = new Cesium.Event();
        this._pickEndEvent = new Cesium.Event();
        this._pickStartEvent = new Cesium.Event();
        
        // 创建事件处理类
        var screenSpaceEventHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);

        var lastCursorPosScratch1 = new Cesium.Cartesian3();
        var lastCursorPos1 = undefined;
        var lastCursorPosScratch2 = new Cesium.Cartesian3();
        var lastCursorPos2 = undefined;

        var altLeftDownAction = movement => {
            lastCursorPos1 = Cesium.Cartesian3.clone(movement.position, lastCursorPosScratch1);

            if (this._mode === 'polygon') {
                this._canvasManager.positions.splice(0, this._canvasManager.positions.length, movement.position.x, movement.position.y);
                this._canvasManager.refresh();
            }

            this._pickStartEvent.raiseEvent();
        }

        var altMouseMoveAction = movement => {
            if (lastCursorPos1) {
                if (this._mode === 'rect') {
                    lastCursorPos2 = Cesium.Cartesian3.clone(movement.endPosition, lastCursorPosScratch2);
                    getViewportRectangle(viewer, lastCursorPos1, lastCursorPos2, rect.rectangle);
                    rect.show = true;
                } else if (this._mode === 'polygon') {
                    const ps = this._canvasManager.positions;
                    const p = movement.endPosition;
                    if (p.x !== ps[2*(ps.length - 1)+0] ||
                    p.y !== ps[2*(ps.length - 1)+1]) {
                        ps.push(p.x, p.y);
                    }
                    this._canvasManager.refresh();
                }
            }
        }

        var altLeftUpAction = movement => {
            this._pickEndEvent.raiseEvent();

            if (this._mode === 'rect') {
                if (lastCursorPos1 && lastCursorPos2) {
                    // var pickedPois = boxPick(viewer.scene, lastCursorPos1, lastCursorPos2, this._pickResult);
                    // var pickedPois = boxPick(viewer.scene, { rectPositions: [lastCursorPos1.x, lastCursorPos1.y, lastCursorPos2.x, lastCursorPos2.y] }, this._pickResult);
                    var pickedPois = viewer.scene.xbsjBoxPick(lastCursorPos1, lastCursorPos2, this._pickResult);
                    this._objectsPickedEvent.raiseEvent(pickedPois);
                }
            } else if (this._mode === 'polygon') {
                // var pickedPois = boxPick(viewer.scene, { filterPolygonPositions: this._canvasManager.positions }, this._pickResult);
                var pickedPois = viewer.scene.xbsjPolygonPick(this._canvasManager.positions, this._pickResult);
                this._objectsPickedEvent.raiseEvent(pickedPois);
            }

            this._canvasManager.positions.splice(0, this._canvasManager.positions.length);
            this._canvasManager.refresh();

            lastCursorPos1 = undefined;
            lastCursorPos2 = undefined;
            rect.show = false;
        }

        this._enableAction = () => {
            screenSpaceEventHandler.setInputAction(altLeftDownAction, Cesium.ScreenSpaceEventType.LEFT_DOWN, Cesium.KeyboardEventModifier.ALT);
            screenSpaceEventHandler.setInputAction(altMouseMoveAction, Cesium.ScreenSpaceEventType.MOUSE_MOVE, Cesium.KeyboardEventModifier.ALT);
            screenSpaceEventHandler.setInputAction(altMouseMoveAction, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
            screenSpaceEventHandler.setInputAction(altLeftUpAction, Cesium.ScreenSpaceEventType.LEFT_UP, Cesium.KeyboardEventModifier.ALT);
            screenSpaceEventHandler.setInputAction(altLeftUpAction, Cesium.ScreenSpaceEventType.LEFT_UP, Cesium.KeyboardEventModifier.CTRL);
            screenSpaceEventHandler.setInputAction(altLeftUpAction, Cesium.ScreenSpaceEventType.LEFT_UP, Cesium.KeyboardEventModifier.SHIFT);
            screenSpaceEventHandler.setInputAction(altLeftUpAction, Cesium.ScreenSpaceEventType.LEFT_UP);
        }

        this._disableAction = () => {
            screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOWN, Cesium.KeyboardEventModifier.ALT);
            screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE, Cesium.KeyboardEventModifier.ALT);
            screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE);
            screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_UP, Cesium.KeyboardEventModifier.ALT);
            screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_UP, Cesium.KeyboardEventModifier.CTRL);
            screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_UP, Cesium.KeyboardEventModifier.SHIFT);
            screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_UP);
        }

        this._screenSpaceEventHandler = screenSpaceEventHandler;

        this._pickResult = [];

        this._enabled = true;
        this.enabled = false;
    }

    set mode(val) {
        if (val !== 'polygon' && val !== 'rect') {
            console.error('mode设置错误!');
        }

        if (this._mode !== val) {
            this._mode = val;
        }
    }

    get mode() {
        return this._mode;
    }

    isDestroyed() {
        return false;
    }

    destroy() {
        this._viewer.scene.primitives.remove(this._rect);
        this._screenSpaceEventHandler = this._screenSpaceEventHandler && this._screenSpaceEventHandler.destroy();
        return Cesium.destroyObject(this);
    }
}

Object.defineProperties(XbsjBoxPickAction.prototype, {
    /**
     * 框选操作拾取事件
     * @description 回调使用BoxPickedCallback
     * @memberof XbsjBoxPickAction.prototype
     * @type {Cesium.Event}
     * @readonly
     * @see XbsjBoxPickAction.BoxPickedCallback
     * @example
     * xbsjBoxPickAction.objectsPicked.addEventListener(objects => {
     *     console.log(`objectsPicked: ${objects.length}`);
     * });
     */
    objectsPicked: {
        get() {
            return this._objectsPickedEvent;
        }
    },
    /**
     * 框选操作开始事件
     * @memberof XbsjBoxPickAction.prototype
     * @type {Cesium.Event}
     * @readonly
     */
    pickStart: {
        get() {
            return this._pickStartEvent;
        }
    },
    /**
     * 框选操作结束事件
     * @memberof XbsjBoxPickAction.prototype
     * @type {Cesium.Event}
     * @readonly
     */
    pickEnd: {
        get() {
            return this._pickEndEvent;
        }
    },
    /**
     * 框选操作是否启用
     * @memberof XbsjBoxPickAction.prototype
     * @type {boolean}
     */
    enabled: {
        get() {
            return this._enabled;
        },
        set(value) {
            value = !!value;
            if (this._enabled !== value) {
                value ? this._enableAction() : this._disableAction();
                this._enabled = value;
            }
        }
    }
});
    
    </script>
    <script>
        var earth;
        var bgImagery;

        function startup() {
            // earth = new XE.Earth('earthContainer');
            earth = new XE.Earth('earthContainer', {
                // 这里设置Viewer的配置，和new Viewer(container, options)中的options一致
                homeButton: true,
                timeline: true,
            });

            earth.sceneTree.root = {
                "children": [
                    {
                        "czmObject": {
                            "name": "默认离线影像",
                            "xbsjType": "Imagery",
                            "xbsjImageryProvider": {
                                "createTileMapServiceImageryProvider": {
                                    "url": XE.HTML.cesiumDir + 'Assets/Textures/NaturalEarthII',
                                    "fileExtension": 'jpg',
                                },
                                "type": "createTileMapServiceImageryProvider"
                            }
                        }
                    },
                ]
            };

            const viewer = earth.czm.viewer;

            alert('按住alt键进行任意多选测试！');

            // 1 poi点准备
            var favLongitude = Cesium.Math.toDegrees(2.0937754841837863);
            var favLatitude = Cesium.Math.toDegrees(0.5305995567057125);

            Cesium.GeoJsonDataSource.markerSize = 48;
            Cesium.GeoJsonDataSource.crsNames['vtxfcrs'] = function (coordinates) {
                return Cesium.Cartesian3.fromDegrees(coordinates[0]*0.005 + favLongitude - 0.005, coordinates[1]*0.005 + favLatitude + 0.002, 50.0);
            }

            var markerDataSourceUrl = './assets/simplestyles.geojson';
            createMarkerDataSource(viewer, markerDataSourceUrl, 'vtxfcrs').then(function (dataSource) {
                viewer.zoomTo(dataSource);
            });

            // 2 拾取操作设置(测试用)
            // var xbsjBoxPickAction = new Cesium.XbsjBoxPickAction(viewer);
            var xbsjBoxPickAction = new XbsjBoxPickAction(viewer);
            xbsjBoxPickAction.enabled = true;
            xbsjBoxPickAction.objectsPicked.addEventListener(objects => {
                console.log(`objectsPicked: ${objects.length}`);
                alert(`objectsPicked: ${objects.length}`);
            });
            // xbsjBoxPickAction.mode = 'polygon'; // 任意选择

            window.xbsjBoxPickAction = xbsjBoxPickAction;
            // xbpa.mode = 'rect'; // 框选
            // xbpa.pickStart.addEventListener(() => {
            //     console.log('boxPick start!');
            // });
            // xbpa.pickEnd.addEventListener(() => {
            //     console.log('boxPick end!');
            // });

            // 3 直接调用的方法：
            // 3.1 框选 参数为屏幕坐标下的起始点和终点
            // var pickedObjects = viewer.scene.xbsjBoxPick({x: 0, y: 0}, {x: 100, y: 100});
            // 3.2 多边形选择 参数屏幕坐标下的多个点[x0, y0, x1, y1, x2, y2, ...]
            // var pickedObjects = viewer.scene.xbsjPolygonPick([0, 0, 100, 100, 50, 200, 200, 50]);
        }

        function createMarkerDataSource(viewer, markerDataSourceUrl, crsString) {
            var markerDataSource;

            var geoJsonObjectPromise = Cesium.Resource.fetchJson({
                url: markerDataSourceUrl
            });

            return geoJsonObjectPromise.then(function (geoJsonObject) {
                geoJsonObject.crs = {
                    type: 'name',
                    properties: {
                        name: crsString
                    }
                };
                markerDataSource = new Cesium.GeoJsonDataSource('markerDataSource');
                var promise = markerDataSource.load(geoJsonObject);
                viewer.dataSources.add(markerDataSource);

                var distanceDisplayCondition = new Cesium.DistanceDisplayCondition(0.0, 1000000.0);
                var translucencyByDistance = new Cesium.NearFarScalar(500000, 1.0, 1000000, 0.0);

                return promise.then(function (dataSource) {
                    dataSource.entities.values.forEach(function (entity) {
                        entity.point = new Cesium.PointGraphics({
                            pixelSize: 5.0,
                            color: Cesium.Color.YELLOW,
                            outlineColor: Cesium.Color.BLACK,
                            outlineWidth: 1.0,
                            distanceDisplayCondition: distanceDisplayCondition,
                            translucencyByDistance: translucencyByDistance,
                        });
                        entity.billboard.show = false;
                        entity.billboard.distanceDisplayCondition = distanceDisplayCondition;
                        entity.billboard.translucencyByDistance = translucencyByDistance;
                        entity.billboard.scale = 0.001;
                        entity.poi = true;
                    });

                    return dataSource;
                });
            });
        }        

        // 1 XE.ready()会加载Cesium.js等其他资源，注意ready()返回一个Promise对象。
        XE.ready().then(startup);            
    </script>
</body>

</html>